/*
 * $Id: JsonRpcClient.java 42143 2007-01-30 18:01:51Z jdamick $
 *
 * Copyright 2006- Revolution Health Group.  All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Revolution Health Group.  (Confidential Information).
 * You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license 
 * agreement you entered into with Revolution Health Group.
 *
 */
package org.javason.jsonrpc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.log4j.Logger;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.javason.test.ElapsedTime;
import org.javason.util.Debug;

public class JsonRpcClient {
	private static final Logger LOG = Logger.getLogger(JsonRpcClient.class);
	public static final String VERSION = "$Rev: 42143 $";

	private int _soTimeout = 5000;
	private int _maxConnectionsPerHost = 10;
	private int _retryCount = 0;
	private String _urlEndpoint;
	private int _statusCode = HttpStatus.SC_OK;
	private boolean _benchmarkEnabled = false;


	/**
	 * @return Returns the benchmarkEnabled.
	 */
	public boolean isBenchmarkEnabled() {
		return _benchmarkEnabled;
	}

	/**
	 * @param benchmarkEnabled The benchmarkEnabled to set.
	 */
	public void setBenchmarkEnabled(boolean benchmarkEnabled) {
		_benchmarkEnabled = benchmarkEnabled;
	}
	
	/**
	 * @return Returns the statusCode.
	 */
	public int getStatusCode() {
		return _statusCode;
	}
	
	/**
	 * @return Returns the retryCount.
	 */
	public int getRetryCount() {
		return _retryCount;
	}

	/**
	 * @param retryCount The retryCount to set.
	 */
	public void setRetryCount(int retryCount) {
		_retryCount = retryCount;
	}
	
	
	/**
	 * @return Returns the urlEndpoint.
	 */
	public String getUrlEndpoint() {
		return _urlEndpoint;
	}


	/**
	 * @param urlEndpoint The urlEndpoint to set.
	 */
	public void setUrlEndpoint(String urlEndpoint) {
		_urlEndpoint = urlEndpoint;
	}
	

	public int getMaxConnectionsPerHost() {
		return _maxConnectionsPerHost;
	}

	public void setMaxConnectionsPerHost(int connectionsPerHost) {
		_maxConnectionsPerHost = connectionsPerHost;
	}

	
	
	/**
	 * @return the SO_TIMEOUT
	 */
	public int getSoTimeout() {
		return _soTimeout;
	}

	/**
	 * @param timeout the SO_TIMEOUT to set
	 */
	public void setSoTimeout(int soTimeout) {
		_soTimeout = soTimeout;
	}

	public JSONObject callMethod(String method, Object ... params) {
		JSONObject jsonReq = JsonRpcRequestFactory.createJsonRpcRequest(method, params);
		PostMethod postMethod = new PostMethod(_urlEndpoint);
		StringRequestEntity entity;
		String response = null;
		try {
			entity = new StringRequestEntity(jsonReq.toString().trim(), 
					JsonRpcConstants.CLIENT_REQUEST_CONTENT_TYPE, "UTF-8");
		
			postMethod.setRequestEntity(entity);
			Debug.log(LOG, "Request content-length: %s", entity.getContentLength());	
			response = makeRequest(postMethod);
		} catch (UnsupportedEncodingException e) {
			LOG.error("Error encoding body", e);
		}
		JSONObject result = null;
		try {
			result = JSONObject.fromObject(response);
		} catch (JSONException e) {
			LOG.error("Error JSONObject", e);
		}
		return result;
	}
	
	private String makeRequest(HttpMethod method) {
		// Create an instance of HttpClient.
	    HttpClient client = new HttpClient();

	    try {
			Debug.log(LOG, "hitting: " + method.getURI());
		} catch (URIException e1) {
			Debug.log(LOG, e1, "Error creating URI");
		}
	    
	    // Provide custom retry handler is necessary
	    method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
	    		new DefaultHttpMethodRetryHandler(_retryCount, false));

	    method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
	    method.getParams().setSoTimeout(_soTimeout);
	    client.getHttpConnectionManager().getParams().setDefaultMaxConnectionsPerHost(_maxConnectionsPerHost);
	    
		String result = null;
		try {
			ElapsedTime time = null;
			if (_benchmarkEnabled) {
				time = new ElapsedTime();
				time.begin();
			}
			
			// Execute the method.
			_statusCode = client.executeMethod(method);
			
			if (_benchmarkEnabled && time != null) {
				time.end();
				time.printElapsedTime();
				Debug.log(LOG, time.toString());
	    	}
	    	

			if (_statusCode != HttpStatus.SC_OK) {
				Debug.log(LOG, "Method failed: %s", method.getStatusLine());
			}
			
			debugHeaders(method.getResponseHeaders());

			// Read the response body.
			result = getResponse(method.getResponseBodyAsStream());

	    } catch (HttpException e) {
	    	LOG.error("Fatal protocol violation: ", e);
	    } catch (IOException e) {
	    	LOG.error("Fatal transport error", e);
	    } finally {
	    	//Release the connection.
	    	method.releaseConnection();
	    }
	    return result;
	}
	
	private void debugHeaders(Header[] headers) {
		if (LOG.isDebugEnabled()) {
			Debug.log(LOG, "-- HEADERS --");
			for (Header header : headers) {
				Debug.log(LOG, "Header: %s", header);
			}
			Debug.log(LOG, "-- END HEADERS --");
		}
	}
	
	private String getResponse(InputStream response) throws IOException {
		StringBuilder result = new StringBuilder();
		BufferedReader inReader = new BufferedReader(new InputStreamReader(response));
		String line = null;
		while ((line = inReader.readLine()) != null) {
			result.append(line);
		}
		String responseBody = result.toString();
		debugResponse(responseBody);
		return responseBody;
	}
	
	private void debugResponse(String response) {
		if (LOG.isDebugEnabled()) {
			Debug.log(LOG, "-- RESPONSE ");
			Debug.log(LOG, "%s", response);
			Debug.log(LOG, "-- END RESPONSE ");
		}
	}

}
